/*
 * opencog/query/ConstraintDomain.cc
 *
 * Copyright (C) 2025 BrainyBlaze Dynamics, LLC
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <sstream>
#include <limits>

#include <opencog/util/Logger.h>
#include <opencog/atoms/base/Atom.h>

#include "ConstraintDomain.h"

using namespace opencog;

// Static member initialization
const HandleSet ConstraintDomain::_empty_set;

// -------------------------------------------------------
// Domain initialization
// -------------------------------------------------------

void ConstraintDomain::init_domain(const Handle& var, const HandleSet& possible_values)
{
	_domains[var] = possible_values;
}

void ConstraintDomain::clear()
{
	_domains.clear();
	_initial_domains.clear();
	_bound_vars.clear();
	_neighbors.clear();
	while (!_state_stack.empty()) _state_stack.pop();
}

void ConstraintDomain::save_initial()
{
	_initial_domains = _domains;
}

void ConstraintDomain::reset()
{
	_domains = _initial_domains;
	_bound_vars.clear();
	while (!_state_stack.empty()) _state_stack.pop();
}

// -------------------------------------------------------
// Domain queries
// -------------------------------------------------------

const HandleSet& ConstraintDomain::get_domain(const Handle& var) const
{
	auto it = _domains.find(var);
	if (it == _domains.end()) return _empty_set;
	return it->second;
}

bool ConstraintDomain::has_domain(const Handle& var) const
{
	return _domains.find(var) != _domains.end();
}

bool ConstraintDomain::is_bound(const Handle& var) const
{
	auto it = _domains.find(var);
	if (it == _domains.end()) return false;
	return it->second.size() == 1;
}

bool ConstraintDomain::is_empty(const Handle& var) const
{
	auto it = _domains.find(var);
	if (it == _domains.end()) return true;
	return it->second.empty();
}

Handle ConstraintDomain::get_binding(const Handle& var) const
{
	auto it = _domains.find(var);
	if (it == _domains.end() || it->second.size() != 1)
		return Handle::UNDEFINED;
	return *(it->second.begin());
}

size_t ConstraintDomain::domain_size(const Handle& var) const
{
	auto it = _domains.find(var);
	if (it == _domains.end()) return 0;
	return it->second.size();
}

// -------------------------------------------------------
// Constraint network structure
// -------------------------------------------------------

void ConstraintDomain::add_constraint(const HandleSeq& variables)
{
	// Each variable in the constraint is a neighbor of all others
	for (const Handle& v1 : variables)
	{
		for (const Handle& v2 : variables)
		{
			if (v1 != v2)
				_neighbors[v1].insert(v2);
		}
	}
}

HandleSet ConstraintDomain::get_neighbors(const Handle& var) const
{
	auto it = _neighbors.find(var);
	if (it == _neighbors.end()) return HandleSet();
	return it->second;
}

// -------------------------------------------------------
// Constraint propagation
// -------------------------------------------------------

bool ConstraintDomain::bind(const Handle& var, const Handle& value)
{
	auto it = _domains.find(var);
	if (it == _domains.end())
	{
		// Variable not tracked - just record single-value domain
		_domains[var] = {value};
		_bound_vars.insert(var);
		return true;
	}

	// Check that value is in domain
	if (it->second.find(value) == it->second.end())
	{
		// Value not in domain - conflict!
		return false;
	}

	// Set domain to single value
	it->second = {value};
	_bound_vars.insert(var);

	// Propagate: remove this value from all neighbors' domains
	auto neighbors_it = _neighbors.find(var);
	if (neighbors_it != _neighbors.end())
	{
		for (const Handle& neighbor : neighbors_it->second)
		{
			if (!eliminate(neighbor, value))
				return false;  // Neighbor's domain became empty
		}
	}

	return true;
}

bool ConstraintDomain::eliminate(const Handle& var, const Handle& value)
{
	auto it = _domains.find(var);
	if (it == _domains.end()) return true;  // Not tracking this var

	// If already bound to something else, we're fine
	if (_bound_vars.find(var) != _bound_vars.end())
		return true;

	// Remove value from domain
	it->second.erase(value);

	// Check for empty domain (conflict)
	if (it->second.empty())
		return false;

	return true;
}

Handle ConstraintDomain::find_unit() const
{
	for (const auto& kv : _domains)
	{
		if (kv.second.size() == 1 &&
		    _bound_vars.find(kv.first) == _bound_vars.end())
		{
			return kv.first;
		}
	}
	return Handle::UNDEFINED;
}

Handle ConstraintDomain::most_constrained() const
{
	Handle best = Handle::UNDEFINED;
	size_t min_size = std::numeric_limits<size_t>::max();

	for (const auto& kv : _domains)
	{
		// Skip bound variables and empty domains
		if (_bound_vars.find(kv.first) != _bound_vars.end())
			continue;
		if (kv.second.size() <= 1)
			continue;

		if (kv.second.size() < min_size)
		{
			min_size = kv.second.size();
			best = kv.first;
		}
	}

	return best;
}

// -------------------------------------------------------
// State save/restore for backtracking
// -------------------------------------------------------

void ConstraintDomain::push_state()
{
	State state;
	state.domains = _domains;
	state.bound_vars = _bound_vars;
	_state_stack.push(state);
}

void ConstraintDomain::pop_state()
{
	if (_state_stack.empty()) return;
	State& state = _state_stack.top();
	_domains = state.domains;
	_bound_vars = state.bound_vars;
	_state_stack.pop();
}

void ConstraintDomain::pop_discard()
{
	if (!_state_stack.empty())
		_state_stack.pop();
}

// -------------------------------------------------------
// Debugging
// -------------------------------------------------------

std::string ConstraintDomain::to_string() const
{
	std::ostringstream oss;
	oss << "ConstraintDomain {\n";

	for (const auto& kv : _domains)
	{
		oss << "  " << kv.first->to_short_string() << " : {";
		bool first = true;
		for (const Handle& h : kv.second)
		{
			if (!first) oss << ", ";
			first = false;
			oss << h->to_short_string();
		}
		oss << "}";
		if (_bound_vars.find(kv.first) != _bound_vars.end())
			oss << " [BOUND]";
		oss << "\n";
	}

	oss << "  Constraints:";
	for (const auto& kv : _neighbors)
	{
		oss << "\n    " << kv.first->to_short_string() << " <-> ";
		bool first = true;
		for (const Handle& h : kv.second)
		{
			if (!first) oss << ", ";
			first = false;
			oss << h->to_short_string();
		}
	}
	oss << "\n}\n";

	return oss.str();
}
